package edu.upenn.seas.peerreview.smui.variants

import org.scalatest._

import edu.upenn.seas.peerreview.Id

/**
 * Tests the logical components of ConsoleDecider from the InputStream.
 * Does NOT do any testing of appropriate output, because this is non-
 * standard and up to the decider the exact formatting/wording of the output.
 */
class ConsoleDeciderTest extends FunSuite {
  val opp1 = new Object with Id
  val opp2 = new Object with Id
  val opp3 = new Object with Id
  val opp1Name = IdMapper.makeCanonicalString(0, opp1)
  val opp2Name = IdMapper.makeCanonicalString(1, opp2)
  val opp3Name = IdMapper.makeCanonicalString(2, opp3)
  
  // Creates a BufferedReader given a list of input lines.
  private[this] def makeReader(lines : List[String]) = {
    val contents = lines mkString "\n"
    new java.io.BufferedReader(new java.io.StringReader(contents))
  }
  
  // Creates an invisible PrintWriter
  private[this] def makeWriter() = {
    new java.io.PrintStream(new java.io.ByteArrayOutputStream())
  }
  
  def assertListEquals(a : List[Any], b : List[Any]) {
    val r = Set() ++ a
    val s = Set() ++ b
    if (r != s) {
      println(r)
      println(s)
    }
    assert(r == s)
  }

  test("Place ships - basic valid horizontal") {
    val lines = List("0 0 h", "0 1 h", "0 4 h")
    val expected = List( (Point(0, 0), Point(0, 0)), 
                         (Point(0, 1), Point(1, 1)),   
                         (Point(0, 4), Point(2, 4)) )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(5, 5), List(1, 2, 3))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Place ships - basic valid vertical") {
    val lines = List("0 1 v", "2 2 v", "1 1 v", "3 5 v")
    val expected = List( (Point(0, 1), Point(0, 1)), 
                         (Point(2, 2), Point(2, 3)),
                         (Point(1, 1), Point(1, 2)),   
                         (Point(3, 5), Point(3, 7)) )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(4, 8), List(1, 2, 2, 3))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Place ships - mixed horizontal and vertical valid") {
    val lines = List("2 3 h", "6 3 v", "0 1 v", "3 5 h", "5 8 h")
    val expected = List( (Point(2, 3), Point(2, 3)), 
                         (Point(6, 3), Point(6, 4)),
                         (Point(0, 1), Point(0, 2)),
                         (Point(3, 5), Point(5, 5)),   
                         (Point(5, 8), Point(8, 8)) )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(9, 9), List(1, 2, 2, 3, 4))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Place ships - error if off board dimension") {
    val lines = List("3 3 h", "2 0 h", "1 2 v", "0 1 v")
    val expected = List( (Point(0, 1), Point(0, 2)) )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(3, 3), List(2))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Place ships - error if overlap on endpoint") {
    val lines = List("0 0 h", "0 0 h", "1 0 h", "2 0 h", "2 0 v")
    val expected = List( (Point(0, 0), Point(1, 0)),
                         (Point(2, 0), Point(2, 1))
                        )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(3, 3), List(2, 2))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Place ships - error if overlap midship") {
    val lines = List("3 1 v", "1 1 h", "1 2 h", "2 3 h", "2 4 h", "2 4 h", "2 6 h")
    val expected = List( (Point(3, 1), Point(3, 5)),
                         (Point(2, 6), Point(6, 6))
                        )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    val placements = decider.placeShips( Point(7, 7), List(5, 5))
    
    assertListEquals(expected, placements)

    decider.in.close()
    decider.out.close()
  }
  
  test("Make move - valid sequence") {
    val opps = List(opp1, opp2, opp3)
    
    val lines = List("%s 0 2".format(opp1Name),
                     "%s 2 2".format(opp2Name),
                     "%s 3 2".format(opp1Name),
                     "%s 4 0".format(opp3Name))
    
    val expected = List( (opp1, Point(0, 2)),
                         (opp2, Point(2, 2)),
                         (opp1, Point(3, 2)),
                         (opp3, Point(4, 0))
                        )
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    // Simulate new game.
    decider.onNewGame(opps, Point(5, 5))
    // Assert expected moves made.
    expected.foreach(i => assert(decider.getMoveFromUser(opps, Point(5, 5)) == i))
    decider.in.close()
    decider.out.close()
  }
  
  test("Make move - invalid opponent") {
    val lines = List("%s 2 2".format(opp1Name),
                     "%s 2 0".format(opp1Name), // Should fail
                     "%s 2 2".format(opp2Name),
                     "%s 2 0".format(opp1Name))
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    // Simulate new game.
    val size = Point(5, 5)
    decider.onNewGame(List(opp1, opp2, opp3), size)
    
    // Opp1 (2, 2)
    assert(decider.getMoveFromUser(List(opp1, opp2, opp3), size) == 
        (opp1, Point(2, 2)))
    // Fails on Opp1 (2, 0) because not on opponent list
    assert(decider.getMoveFromUser(List(opp2, opp3), size) == 
        (opp2, Point(2, 2)))
    // Opp1 (2, 0)
    assert(decider.getMoveFromUser(List(opp1, opp2), size) == 
        (opp1, Point(2, 0)))
    decider.in.close()
    decider.out.close()
  }
  
  test("Make move - invalid dimension") {
    val lines = List("%s 2 2".format(opp1Name),
                     "%s 2 8".format(opp1Name), // Should fail
                     "%s 6 1".format(opp1Name), // Should fail
                     "%s -1 3".format(opp1Name), // Should fail
                     "%s 2 0".format(opp1Name))
    val decider = new ConsoleDecider(makeReader(lines), makeWriter())
    // Simulate new game.
    val size = Point(6, 6)
    val opps = List(opp1)
    decider.onNewGame(opps, size)
    
    assert(decider.getMoveFromUser(opps, size) == (opp1, Point(2, 2)))
    // Fails on out-of-bound dimensions and retries.
    assert(decider.getMoveFromUser(opps, size) == (opp1, Point(2, 0)))
    
    decider.in.close()
    decider.out.close()
  }
}
